//
//  TTXMLNode.m
//  Tint
//
//  Created by Kevin Wu on 4/19/12.
//  Copyright (c) 2012 Telligenty. All rights reserved.
//

#import "TTXMLNode.h"

@interface TTXMLNode (Private)

- (NSString *)stringFromXMLString:(const xmlChar *)chars;
- (xmlChar *)XMLStringFromString:(NSString *)string;

- (void)releaseCachedValues;

@end

@implementation TTXMLNode (Private)

- (NSString *)stringFromXMLString:(const xmlChar *)chars {
    return [NSString stringWithUTF8String:(const char *)chars];
}

- (xmlChar *)XMLStringFromString:(NSString *)string {
    return (xmlChar *)[string UTF8String];
}


- (void)releaseCachedValues {
    TTRelease(_cachedName);
    TTRelease(_cachedChildren);
    TTRelease(_cachedAttributes);
}

@end



@implementation TTXMLNode


#pragma mark - Accessors

@synthesize XMLNode = _XMLNode;

- (void)setXMLNode:(xmlNodePtr)XMLNode {
    _XMLNode = XMLNode;
    [self releaseCachedValues];
}


- (NSString *)name {
    if (_cachedName != nil)
        return _cachedName;
    
    if (_XMLNode != NULL) {
        NSString *tmpName = [self stringFromXMLString:_XMLNode->name];
        TTRetain(tmpName, _cachedName);
        return _cachedName;
    }
    
    return nil;
}

- (void)setName:(NSString *)name {
    if (_XMLNode != NULL && name != nil) {
        xmlNodeSetName(_XMLNode, [self XMLStringFromString:name]);
        TTCopy(name, _cachedName);
    }
}


- (NSString *)value {
    if (_XMLNode != NULL) {
        xmlChar *chars = xmlNodeGetContent(_XMLNode);
        if (chars) {
            return [self stringFromXMLString:chars];
            xmlFree(chars);
        }
    }
    return nil;
}

- (void)setValue:(NSString *)value {
    if (_XMLNode != NULL && value != nil) {
        xmlNodeSetContent(_XMLNode, [self XMLStringFromString:value]);
    }
}


- (TTXMLNodeType)type {
    if (_XMLNode != NULL) {
        switch (_XMLNode->type) {
            case XML_ELEMENT_NODE:         return TTXMLNodeElement;
            case XML_ATTRIBUTE_NODE:       return TTXMLNodeAttribute;
            case XML_TEXT_NODE:            return TTXMLNodeText;
            case XML_CDATA_SECTION_NODE:   return TTXMLNodeText;
            case XML_ENTITY_REF_NODE:      return TTXMLNodeEntityDeclaration;
            case XML_ENTITY_NODE:          return TTXMLNodeEntityDeclaration;
            case XML_PI_NODE:              return TTXMLNodeProcessingInstruction;
            case XML_COMMENT_NODE:         return TTXMLNodeComment;
            case XML_DOCUMENT_NODE:        return TTXMLNodeDocument;
            case XML_DOCUMENT_TYPE_NODE:   return TTXMLNodeDocument;
            case XML_DOCUMENT_FRAG_NODE:   return TTXMLNodeDocument;
            case XML_NOTATION_NODE:        return TTXMLNodeNotationDeclaration;
            case XML_HTML_DOCUMENT_NODE:   return TTXMLNodeDocument;
            case XML_DTD_NODE:             return TTXMLNodeDTD;
            case XML_ELEMENT_DECL:         return TTXMLNodeElementDeclaration;
            case XML_ATTRIBUTE_DECL:       return TTXMLNodeAttributeDeclaration;
            case XML_ENTITY_DECL:          return TTXMLNodeEntityDeclaration;
            case XML_NAMESPACE_DECL:       return TTXMLNodeNamespace;
            case XML_XINCLUDE_START:       return TTXMLNodeProcessingInstruction;
            case XML_XINCLUDE_END:         return TTXMLNodeProcessingInstruction;
            case XML_DOCB_DOCUMENT_NODE:   return TTXMLNodeDocument;
        }
    }
    return TTXMLNodeInvalid;
}


- (NSString *)XMLString {
    if (_XMLNode != NULL) {
        xmlBufferPtr buffer = xmlBufferCreate();
        if (buffer) {
            
            if (xmlNodeDump(buffer, NULL, _XMLNode, 0, 0) > -1) {
                NSString *string = [[[NSString alloc] initWithBytes:(xmlBufferContent(buffer))
                                                         length:(xmlBufferLength(buffer))
                                                       encoding:NSUTF8StringEncoding] autorelease];
                
                // Remove leading and trailing whitespace
                NSCharacterSet *whitespace = [NSCharacterSet whitespaceAndNewlineCharacterSet];
                return [string stringByTrimmingCharactersInSet:whitespace];
            }
            xmlBufferFree(buffer);
        }
    }
    return nil;
}




#pragma mark - Memory

- (id)initWithXMLNode:(xmlNodePtr)XMLNode {
    self = [super init];
    if (self) {
        _XMLNode = XMLNode;
    }
    return self;
}

- (void)dealloc {
    TTRelease(_cachedName);
    TTRelease(_cachedChildren);
    TTRelease(_cachedAttributes);
    [super dealloc];
}



#pragma mark - NSObject

- (BOOL)isEqual:(id)object {
    if (TTIsInstance(object, [self class])) {
        TTXMLNode *node = object;
        if (node.XMLNode == self.XMLNode) {
            return YES;
        }
    }
    return NO;
}

- (NSString *)description {
    return self.XMLString;
}



#pragma mark - Public

+ (TTXMLNode *)nodeWithXMLNode:(xmlNodePtr)XMLNode {
    return [[[self alloc] initWithXMLNode:XMLNode] autorelease];
}



- (NSUInteger)childCount {
    if (_cachedChildren != nil)
        return [_cachedChildren count];
    
    if (_XMLNode != NULL) {
        
        NSUInteger count = 0;
        
        xmlNodePtr child = _XMLNode->children;
        
        while (child) {
            ++count;
            child = child->next;
        }
        return count;
    }
    return 0;
}

- (NSArray *)children {
    if (_cachedChildren != nil)
        return _cachedChildren;
    
    if (_XMLNode != NULL) {
        
        NSMutableArray *array = [NSMutableArray array];
        
        xmlNodePtr child = _XMLNode->children;
        
        while (child) {
            [array addObjectIfNotNil:[TTXMLNode nodeWithXMLNode:child]];
            child = child->next;
        }
        
        TTRelease(_cachedChildren);
        if (TTIsArrayWithItems(array)) {
            NSArray *tmp = [NSArray arrayWithArray:array];
            TTRetain(tmp, _cachedChildren);
            return _cachedChildren;
        }
    }
    return nil;
}

- (TTXMLNode *)childAtIndex:(NSUInteger)index {
    return [[self children] objectOrNilAtIndex:index];
}



- (TTXMLNode *)parent {
    if (_XMLNode != NULL && _XMLNode->parent != NULL) {
        return [TTXMLNode nodeWithXMLNode:_XMLNode->parent];
    }
    return nil;
}


- (TTXMLNode *)firstChild {
    if (_XMLNode != NULL && _XMLNode->children != NULL) {
        return [TTXMLNode nodeWithXMLNode:_XMLNode->children];
    }
    return nil;
}

- (TTXMLNode *)firstChildNamed:(NSString *)name {
    TTXMLNode *node = [self firstChild];
    while (node) {
        if ([node.name isEqualToString:name]) {
            return node;
        }
        node = [node nextSibling];
    }
    return nil;
}

- (TTXMLNode *)lastChild {
    if (_XMLNode != NULL && _XMLNode->last != NULL) {
        return [TTXMLNode nodeWithXMLNode:_XMLNode->last];
    }
    return nil;
}

- (TTXMLNode *)lastChildNamed:(NSString *)name {
    TTXMLNode *node = [self lastChild];
    while (node) {
        if ([node.name isEqualToString:name]) {
            return node;
        }
        node = [node previousSibling];
    }
    return nil;
}


- (TTXMLNode *)firstAttribute {
    if (_XMLNode != NULL && _XMLNode->properties != NULL) {
        return [TTXMLNode nodeWithXMLNode:(xmlNodePtr)(_XMLNode->properties)];
    }
    return nil;
}

- (TTXMLNode *)lastAttribute {
    if (_XMLNode != NULL && _XMLNode->properties != NULL) {
        xmlAttrPtr attr = _XMLNode->properties;
        while (attr->next) {
            attr = attr->next;
        }
        return [TTXMLNode nodeWithXMLNode:(xmlNodePtr)(attr)];
    }
    return nil;
}


- (TTXMLNode *)previousSibling {
    if (_XMLNode != NULL && _XMLNode->prev != NULL) {
        return [TTXMLNode nodeWithXMLNode:_XMLNode->prev];
    }
    return nil;
}

- (TTXMLNode *)previousSameNameSibling {
    TTXMLNode *node = [self previousSibling];
    while (node) {
        if ([node.name isEqualToString:self.name]) {
            return node;
        }
        node = [node previousSibling];
    }
    return nil;
}

- (TTXMLNode *)nextSibling {
    if (_XMLNode != NULL && _XMLNode->next != NULL) {
        return [TTXMLNode nodeWithXMLNode:_XMLNode->next];
    }
    return nil;
}

- (TTXMLNode *)nextSameNameSibling {
    TTXMLNode *node = [self nextSibling];
    while (node) {
        if ([node.name isEqualToString:self.name]) {
            return node;
        }
        node = [node nextSibling];
    }
    return nil;
}



- (NSArray *)elements {
    NSMutableArray *array = [NSMutableArray array];
    
    for (int i=0; i<[[self children] count]; ++i) {
        TTXMLNode *node = [[self children] objectAtIndex:i];
        if (node.type == TTXMLNodeElement) {
            [array addObject:node];
        }
    }
    
    if (TTIsArrayWithItems(array)) {
        return [NSArray arrayWithArray:array];
    }
    
    return nil;
}

- (NSArray *)elementsForName:(NSString *)name {
    NSMutableArray *array = [NSMutableArray array];
    
    for (int i=0; i<[[self children] count]; ++i) {
        TTXMLNode *node = [[self children] objectAtIndex:i];
        if (node.type == TTXMLNodeElement && [node.name isEqualToString:name]) {
            [array addObject:node];
        }
    }
    
    if (TTIsArrayWithItems(array)) {
        return [NSArray arrayWithArray:array];
    }
    
    return nil;
}

- (NSArray *)attributes {
    if (_cachedAttributes != nil)
        return _cachedAttributes;
    
    
    if (_XMLNode != NULL && _XMLNode->properties != NULL) {
        
        NSMutableArray *array = [NSMutableArray array];
        
        xmlAttrPtr attr = _XMLNode->properties;
        
        while (attr) {
            [array addObjectIfNotNil:[TTXMLNode nodeWithXMLNode:(xmlNodePtr)attr]];
            attr = attr->next;
        }
        
        TTRelease(_cachedAttributes);
        if (TTIsArrayWithItems(array)) {
            NSArray *tmp = [NSArray arrayWithArray:array];
            TTRetain(tmp, _cachedAttributes);
            return _cachedAttributes;
        }
    }
    return nil;
}

- (TTXMLNode *)attributeForName:(NSString *)name {
    TTXMLNode *node = [self firstAttribute];
    while (node) {
        if ([node.name isEqualToString:name]) {
            return node;
        }
        node = [node nextSibling];
    }
    return nil;
}



- (TTXMLNode *)addElementWithName:(NSString *)name value:(NSString *)value {
    xmlNodePtr element = xmlNewNode(NULL, [self XMLStringFromString:name]);
    
    if (element) {
        
        xmlNodePtr text = xmlNewText([self XMLStringFromString:value]);
        
        if (text) {
            if (xmlAddChild(element, text)) {
                return [self addChild:[TTXMLNode nodeWithXMLNode:element]];
            }
            
            xmlFreeNode(text);
        }
        
        xmlFreeNode(element);
    }
    
    return nil;
}

- (TTXMLNode *)addAttributeWithName:(NSString *)name value:(NSString *)value {
    xmlAttrPtr attr = xmlNewProp(NULL, [self XMLStringFromString:name], [self XMLStringFromString:value]);
    if (attr) {
        return [self addChild:[TTXMLNode nodeWithXMLNode:(xmlNodePtr)attr]];
    }
    return nil;
}

- (TTXMLNode *)addTextWithValue:(NSString *)value {
    xmlNodePtr text = xmlNewText([self XMLStringFromString:value]);
    if (text) {
        return [self addChild:[TTXMLNode nodeWithXMLNode:text]];
    }
    return nil;
}

- (TTXMLNode *)addChild:(TTXMLNode *)child {
    if (_XMLNode == NULL || child.XMLNode == NULL) return nil;
    
    [self releaseCachedValues];
    
    xmlNodePtr newNode = NULL;
    
    if (child.type == TTXMLNodeAttribute) {
        // Ignore this if an attribute with the name is already present
        if (xmlHasProp(_XMLNode, child.XMLNode->name) == NULL)
            newNode = child.XMLNode;
        
    } else {
        newNode = child.XMLNode;
    }
    
    if (newNode) {
        if (xmlAddChild(_XMLNode, newNode)) {
            return child;
        }
        
        xmlFreeNode(child.XMLNode);
        child.XMLNode = NULL;
    }
    
    return nil;
    
}

- (void)removeChild:(TTXMLNode *)child {
    if (_XMLNode != NULL) {
        
        [self releaseCachedValues];
        
        xmlUnlinkNode(child.XMLNode);
        xmlFreeNode(child.XMLNode);
        child.XMLNode = NULL;
    }
}

@end
